package org.luo.lan.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;
import org.luo.lan.client.config.ClientConfig;
import org.luo.lan.client.handlers.ClientBridgeHandler;
import org.luo.lan.client.handlers.ClientProxyHandler;
import org.luo.lan.common.config.SslConfig;
import org.luo.lan.common.util.SslEngineUtil;

import javax.net.ssl.SSLEngine;

/**
 * @Auther: luobiao
 * @Date: 2020/9/19 09:15
 * @Description:
 */
@Slf4j
public class ClientApplication {
    public static final long RE_CONNECT_TIME = 3 * 1000;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private Bootstrap bt;
    private Channel proxyChannel;


    public static void main(String[] args) {
        new ClientApplication();
    }

    private ClientApplication(){
        start();
    }

    private void start() {
        workerGroup = new NioEventLoopGroup();
        bt = new Bootstrap().group(workerGroup).channel(NioSocketChannel.class).handler(new BridgeInitializer(this));
        connect();
    }

    public void connect(){
        String address = ClientConfig.getInstance().getServerAddress();
        int port = ClientConfig.getInstance().getServerPort();
        log.debug("address----->{};port----->{}", address, port);
        bt.connect(address,port).addListener((ChannelFuture future)->{
            if (!future.isSuccess()) {
                log.error("连接服务器超时，将于" + RE_CONNECT_TIME / 1000 + "s后重试！");
                Thread.sleep(RE_CONNECT_TIME);
                connect();
            }else{
                int proxyPort = ClientConfig.getInstance().getProxyPort();
                if ( proxyPort!= 0) {
                    //启动客户端代理端口
                    bossGroup = new NioEventLoopGroup(1);
                    bindServerPort(new ProxyInitializer((NioSocketChannel) future.channel()), proxyPort).addListener((ChannelFuture proxyFuture) -> {
                        if (proxyFuture.isSuccess()) {
                            this.proxyChannel = proxyFuture.channel();
                            log.debug("客户端代理端口启动成功！");
                        } else {
                            log.error("客户端代理端口启动失败！失败原因：{}", proxyFuture.cause().getMessage());
                        }
                    });
                }
            }
        });
    }

    public void closeProxyChannle(){
        if (proxyChannel != null) {
            this.proxyChannel.close();
            log.debug("客户端代理已关闭！");
        }
    }

    public class BridgeInitializer extends ChannelInitializer<SocketChannel> {
        ClientApplication application;

        public BridgeInitializer(ClientApplication application) {
            this.application=application;
        }

        @Override
        public void initChannel(SocketChannel ch) {
            ChannelPipeline pipeline = ch.pipeline();
            SslConfig sslConfig = SslConfig.builder(ClientConfig.getInstance().getSslPwd(), ClientConfig.getInstance().getSslFileName())
                    .useClientMode(true).needClientAuth(true).build();

            SSLEngine engine = SslEngineUtil.getSslEngine(sslConfig);
            engine.setUseClientMode(true);
            pipeline.addLast("ssl", new SslHandler(engine));
            ch.pipeline().addLast(new ObjectEncoder());
            ch.pipeline().addLast(new ObjectDecoder((className)-> Class.forName(className)));
            pipeline.addLast(new ClientBridgeHandler(application));
        }
    }

    public class ProxyInitializer extends ChannelInitializer<SocketChannel> {

        NioSocketChannel bridgeChannel;

        public ProxyInitializer(NioSocketChannel bridgeChannel) {
            this.bridgeChannel = bridgeChannel;
        }

        @Override
        protected void initChannel(SocketChannel ch) {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new ClientProxyHandler(bridgeChannel));
        }
    }


    public ChannelFuture bindServerPort(ChannelInitializer initializer, int port) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        return serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(initializer)
                .bind(port);
    }
}
